package core.net.protocol;

import core.buffer.Buffer;
import core.buffer.BufferManager;
import core.net.NetConfig;

import java.lang.reflect.InvocationTargetException;
import java.util.*;

public class Protocol {

    private static Protocol _instance = new Protocol();
    public static Protocol instance() {
        return _instance;
    }
    public static final int TYPE_SIZE = 1;
    private BufferManager _bufferManager = new BufferManager();
    private Map<Byte, Class<?>> _types = new HashMap<>();
    private Map<Byte, Stack<IProtocol>> _protos = new HashMap<>();

    private Protocol() {

    }

    @SuppressWarnings("unchecked")
    public <T extends IProtocol> T get(Byte type) {
        Stack<IProtocol> protocols = _protos.get(type);
        if(protocols == null) {
            protocols = new Stack<>();
            _protos.put(type, protocols);
        }
        IProtocol t = protocols.isEmpty() ? null : protocols.pop();
        if (t == null) {
            Class<?> c = _types.get(type);
            if(c  == null)
            {
                return null;
            }
            try {
                t = (IProtocol) _types.get(type).getDeclaredConstructor().newInstance();
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                     NoSuchMethodException e) {
                return null;
            }
        }
        return (T) t;
    }

    public void put(IProtocol data) {
        try {
            Stack<IProtocol> protocols = _protos.computeIfAbsent(data.protocolType(), k -> new Stack<>());
            protocols.add(data);
        }catch (Exception e)
        {
            System.out.println(e);
        }
    }

    public void register(byte pt, Class<?> type) {
        _types.put(pt, type);
    }

    public Buffer serialize(IProtocol protocol) {
        Buffer buffer = _bufferManager.apply(protocol.size() + TYPE_SIZE + Packet.PACK_SIZE);
        buffer.write((byte) (protocol.size() + TYPE_SIZE));
        buffer.write(protocol.protocolType());
        Buffer data = protocol.serialize(buffer);
        return data;
    }

    public IProtocol deserialize(Buffer data) {
        byte type = data.read();
        IProtocol protocol = get(type);
        protocol.deserialize(data);
        return protocol;
    }
}
